﻿#region License

// Dimebrain TweetSharp
// (www.dimebrain.com)
// 
// The MIT License
// 
// Copyright (c) 2009 Dimebrain
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if !SILVERLIGHT
using HttpUtility = System.Web.HttpUtility;
#else
using HttpUtility = System.Windows.Browser.HttpUtility;
#endif
using Dimebrain.TweetSharp.Core.Exceptions;
using Dimebrain.TweetSharp.Core.Extensions;
using Dimebrain.TweetSharp.Core.Web;
using Dimebrain.TweetSharp.Fluent.Twitter.Authentication;
using Dimebrain.TweetSharp.Model;

namespace Dimebrain.TweetSharp.Fluent
{
    public partial class IFluentTwitterExtensions
    {
        private const string URL_AUTHORITY = "http://twitter.com/";
        private const string URL_SEARCH_AUTHORITY = "http://search.twitter.com/";

        private const string URL_ACTION_BASE = URL_AUTHORITY + "{0}/{1}.{2}";
        private const string URL_ACTION_ID_BASE = URL_AUTHORITY + "{0}/{1}/{2}.{3}";
        private const string URL_BASE = URL_AUTHORITY + "{0}.{1}";
        private const string URL_SEARCH_BASE = URL_SEARCH_AUTHORITY + "{0}.{1}";
        private const string URL_OAUTH_BASE = URL_AUTHORITY + "oauth/{0}";
        
        public static string AsUrl(this IFluentTwitter instance)
        {
            if(!instance.Parameters.DirectPath.IsNullOrBlank())
            {
                return BuildDirectQuery(instance);
            }

            var hasAuthAction = IsOAuthProcessCall(instance);
            var hasAction = !instance.Parameters.Action.IsNullOrBlank();
            var format = instance.Format.ToLower();
            var activity = instance.Parameters.Activity.IsNullOrBlank() ? "?" : instance.Parameters.Activity;
            var action = hasAction ? instance.Parameters.Action : "?";

            // this is an oauth call
            if(hasAuthAction)
            {
                return BuildOAuthQuery(instance);
            }

            // this is a search api call
            if (hasAction && 
                  (Equals(instance.Parameters.Action, "search") 
                || Equals(instance.Parameters.Action, "trends")
                || Equals(instance.Parameters.Action, "trends/current")
                || Equals(instance.Parameters.Action, "trends/daily")
                || Equals(instance.Parameters.Action, "trends/weekly")))
            {
                return BuildSearchQuery(instance, format);
            }

            // this is a rest api call
            return BuildQuery(instance, hasAction, format, activity, action);
        }

        private static bool IsOAuthProcessCall(IFluentTwitter instance)
        {
            if (instance.Authentication.Mode != AuthenticationMode.OAuth)
            {
                return false;
            }

            if(instance.Authentication == null)
            {
                return false;
            }

            if(!(instance.Authentication.Authenticator is FluentTwitterOAuth))
            {
                return false;
            }

            var oauth = (FluentTwitterOAuth) instance.Authentication.Authenticator;
            return !oauth.Action.Equals("resource");
        }

        private static string BuildOAuthQuery(IFluentTwitter instance)
        {
            var oAuthBase = URL_OAUTH_BASE;
            if (!instance.Configuration.TransparentProxy.IsNullOrBlank())
            {
                var authority = instance.Configuration.TransparentProxy;
                oAuthBase = oAuthBase.Replace(URL_AUTHORITY, authority);
            }

            var oauth = (FluentTwitterOAuth)instance.Authentication.Authenticator;
            var url = oAuthBase.FormatWith(oauth.Action);
            if (oauth.Action == "access_token" && !oauth.Verifier.IsNullOrBlank() )
            {
                var delim = url.Contains( "?" ) ? '&' : '?';
                url += delim;
                url += "oauth_verifier={0}".FormatWith(oauth.Verifier);
            }

            return oauth.Action == "authorize" ? BuildOAuthParameters(oauth, url) : url;
        }

        private static string BuildOAuthParameters(IFluentTwitterOAuth oauth, string url)
        {
            var parameters = new List<string>(0);
            if(!oauth.Token.IsNullOrBlank())
            {
                parameters.Add("oauth_token={0}".FormatWith(oauth.Token));
            }

            if(!oauth.Callback.IsNullOrBlank())
            {
                parameters.Add("oauth_callback={0}".FormatWith(oauth.Callback));
            }

            var sb = new StringBuilder(url);
            for (var i = 0; i < parameters.Count(); i++)
            {
                sb.Append(i > 0 ? "&" : "?");
                sb.Append(parameters[i]);
            }

            return sb.ToString();
        }

        private static string BuildQuery(IFluentTwitter instance, bool hasAction, string format, string activity, string action)
        {
            var id = !instance.Parameters.ScreenName.IsNullOrBlank()
                         ? instance.Parameters.ScreenName
                         : !instance.Parameters.Email.IsNullOrBlank()
                               ? instance.Parameters.Email
                               : instance.Parameters.Id.HasValue
                                     ? instance.Parameters.Id.Value.ToString()
                                     : String.Empty;

            var isDisambiguated =
                instance.Parameters.Activity.Equals("users") &&
                (!instance.Parameters.Action.IsNullOrBlank() && 
                  instance.Parameters.Action.Equals("show"));
          
            var hasId = !id.IsNullOrBlank() && !isDisambiguated;

            // Swap the authority if a transparent proxy is used
            var urlActionIdBase = URL_ACTION_ID_BASE;
            var urlActionBase = URL_ACTION_BASE;
            var urlBase = URL_BASE;
            if(!instance.Configuration.TransparentProxy.IsNullOrBlank())
            {
                var authority = instance.Configuration.TransparentProxy;
                urlActionIdBase = urlActionIdBase.Replace(URL_AUTHORITY, authority);
                urlActionBase = urlActionBase.Replace(URL_AUTHORITY, authority);
                urlBase = urlBase.Replace(URL_AUTHORITY, authority);
            }

            var url = hasAction ? hasId ? urlActionIdBase : urlActionBase : urlBase;
            url = String.Format(url, hasAction
                                         ? hasId
                                               ? new object[] {activity, action, id, format}
                                               : new object[] {activity, action, format}
                                         : new object[] {activity, format});

            var sb = new StringBuilder(url);
            
            var parameters = new List<string>(BuildParameters(instance));
            for (var i = 0; i < parameters.Count(); i++)
            {
                sb.Append(i > 0 ? "&" : "?");
                sb.Append(parameters[i]);
            }

            var resultUrl = sb.ToString();
            return resultUrl;
        }

        private static string BuildDirectQuery(IFluentTwitter instance)
        {
            var path = instance.Parameters.DirectPath;
            if (path.StartsWith("/"))
            {
                path = path.Substring(1);
            }

            var url = String.Concat(URL_AUTHORITY, path);
            var uri = url.AsUri();

            var query = uri.Query;
            if (query.IsNullOrBlank())
            {
                path = url;
            }
            else
            {
                // remove POST parameters // NOTE: Duplicate from Core.Web.Query.OAuth.OAuthWebQuery.BuildPostWebRequest()
                path = uri.Scheme.Then("://")
#if !SILVERLIGHT
                    .Then(uri.Authority)
#else
                    .Then(uri.Host)
#endif
                ;
                if (uri.Port != 80)
                    path = path.Then(":" + uri.Port);
                path = path.Then(uri.AbsolutePath);

#if !SILVERLIGHT
                var parameters = HttpUtility.ParseQueryString(query);
#else
                var parameters = StringExtensions.ParseQueryString(query);
#endif

                var encodedQuery = "?" + string.Join("&", parameters
#if !SILVERLIGHT
                    .AllKeys
#else
                    .Keys
#endif
                        .Select(key => "{0}={1}".FormatWith(
                            HttpUtility.UrlEncode(key),
                            HttpUtility.UrlEncode(parameters[key]))).ToArray());

                path = path.Then(encodedQuery);
            }

            return path;
        }

        private static string BuildSearchQuery(IFluentTwitter instance, string format)
        {
            // Swap the authority if a transparent proxy is used
            var urlSearchBase = URL_SEARCH_BASE;
            if (!instance.Configuration.TransparentProxy.IsNullOrBlank())
            {
                var authority = instance.Configuration.TransparentProxy;
                urlSearchBase = urlSearchBase.Replace(URL_SEARCH_AUTHORITY, authority);
            }

            var searchUrl = urlSearchBase.FormatWith(instance.Parameters.Action, format);
            var searchBuilder = new StringBuilder(searchUrl);

            // [Issue 2] Distinguish between parameters and operators
            var searchOperators = new List<string>(BuildSearchOperators(instance));
            for (var i = 0; i < searchOperators.Count(); i++)
            {
                searchBuilder.Append(i > 0 ? "+" : "?q=");
                searchBuilder.Append(searchOperators[i]);
            }

            var hasOperators = searchOperators.Count > 0;
            var searchParameters = new List<string>(BuildSearchParameters(instance));
            for (var i = 0; i < searchParameters.Count(); i++)
            {
                searchBuilder.Append(i > 0 || hasOperators ? "&" : "?");
                searchBuilder.Append(searchParameters[i]);
            }
                
            var result = searchBuilder.ToString();
            return result;
        }

        private static IEnumerable<string> BuildSearchOperators(IFluentTwitter instance)
        {
            if (!instance.SearchParameters.SearchPhrase.IsNullOrBlank())
            {
                yield return instance.SearchParameters.SearchPhrase.UrlEncode();
            }

            if (!instance.SearchParameters.SearchWithoutPhrase.IsNullOrBlank())
            {
                yield return "-{0}".FormatWith(instance.SearchParameters.SearchWithoutPhrase).UrlEncode();
            }

            // operators below phrase

            if (instance.SearchParameters.SearchSince.HasValue)
            {
                var date = instance.SearchParameters.SearchSince.Value;
                yield return
                    string.Format("since:{0}-{1}-{2}", date.Year, 
                                                       date.Month.ToString("00"), 
                                                       date.Day.ToString("00")).UrlEncode();
            }

            if (instance.SearchParameters.SearchSinceUntil.HasValue)
            {
                var date = instance.SearchParameters.SearchSinceUntil.Value;
                yield return
                    string.Format("until:{0}-{1}-{2}",
                                  date.Year,
                                  date.Month.ToString("00"),
                                  date.Day.ToString("00")).UrlEncode();
            }

            if (!instance.SearchParameters.SearchFromUser.IsNullOrBlank())
            {
                yield return "from:{0}".FormatWith(instance.SearchParameters.SearchFromUser).UrlEncode();
            }

            if (!instance.SearchParameters.SearchToUser.IsNullOrBlank())
            {
                yield return string.Format("to:{0}", instance.SearchParameters.SearchToUser).UrlEncode();
            }

            if (!instance.SearchParameters.SearchHashTag.IsNullOrBlank())
            {
                yield return string.Format("#{0}", instance.SearchParameters.SearchHashTag.Replace("#", "")).UrlEncode();
            }

            if (!instance.SearchParameters.SearchReferences.IsNullOrBlank())
            {
                yield return string.Format("@{0}", instance.SearchParameters.SearchReferences).UrlEncode();
            }

            if (!instance.SearchParameters.SearchNear.IsNullOrBlank())
            {
                yield return string.Format("near:{0}", instance.SearchParameters.SearchNear).UrlEncode();
            }

            if (instance.SearchParameters.SearchNegativity.HasValue &&
                instance.SearchParameters.SearchNegativity.Value)
            {
                yield return ":(".UrlEncode();
            }

            if (instance.SearchParameters.SearchPositivity.HasValue &&
                instance.SearchParameters.SearchPositivity.Value)
            {
                yield return ":)".UrlEncode();
            }

            if (instance.SearchParameters.SearchQuestion.HasValue &&
                instance.SearchParameters.SearchQuestion.Value)
            {
                yield return "?".UrlEncode();
            }

            if (instance.SearchParameters.SearchContainingLinks.HasValue &&
                instance.SearchParameters.SearchContainingLinks.Value)
            {
                yield return "filter:links".UrlEncode();
            }
        }

        private static IEnumerable<string> BuildSearchParameters(IFluentTwitter instance)
        {
            if (instance.Parameters.SinceId.HasValue)
            {
                yield return string.Format("since_id={0}", instance.Parameters.SinceId.Value);
            }

            if (instance.Parameters.MaxId.HasValue)
            {
                yield return string.Format("max_id={0}", instance.Parameters.MaxId.Value); // Note: Although it isn't documented, it is set in the result's next_page
            }

            if (instance.Parameters.ReturnPerPage.HasValue)
            {
                yield return "rpp={0}".FormatWith(instance.Parameters.ReturnPerPage.Value);
            }

            if (instance.Parameters.Page.HasValue)
            {
                yield return "page={0}".FormatWith(instance.Parameters.Page.Value);
            }

            // root parameters above

            if (!instance.SearchParameters.SearchLanguage.IsNullOrBlank())
            {
                yield return "lang={0}".FormatWith(instance.SearchParameters.SearchLanguage.Substring(0, 2)).ToLower();
            }
            
            // within can be "within:" or "geocode?"
            if (instance.SearchParameters.SearchMiles.HasValue)
            {
                if (instance.SearchParameters.SearchGeoLatitude.HasValue && instance.SearchParameters.SearchGeoLongitude.HasValue)
                {
                    var lat = instance.SearchParameters.SearchGeoLatitude.Value;
                    var lon = instance.SearchParameters.SearchGeoLongitude.Value;
                    var mi = instance.SearchParameters.SearchMiles.Value;

                    // todo confirm precision of units
                    yield return "geocode={0}".FormatWith("{0},{1},{2}mi".FormatWith(lat, lon, mi).UrlEncode());
                }
                else
                {
                    var miles = Convert.ToInt32(instance.SearchParameters.SearchMiles.Value);
                    yield return string.Format("within:{0}mi", miles).UrlEncode();

                    // [Issue 1] Can't use the "near:" + "within:" operator with arbitrary locations 
                    throw new NotSupportedException("You must specify a geo location with Of(latitude, longitude) when using Within(double miles)");
                }
            }

            // [Issue 2]: Missing "show_user" parameter
            if(instance.SearchParameters.SearchShowUser.HasValue)
            {
                yield return "show_user={0}".FormatWith(instance.SearchParameters.SearchShowUser.Value.ToString().ToLower());
            }

            // trend parameters below 

            if (instance.SearchParameters.SearchExcludesHashtags.HasValue)
            {
                yield return "exclude=hashtags";
            }
            if ( instance.SearchParameters.SearchDate.HasValue )
            {
                yield return string.Format("date={0}", instance.SearchParameters.SearchDate.Value.ToString("yyyy-MM-dd")); 
            }
        }

        private static IEnumerable<string> BuildParameters(IFluentTwitter instance)
        {
            if (instance.Parameters.SinceId.HasValue)
            {
                yield return string.Format("since_id={0}", instance.Parameters.SinceId.Value);
            }

            if (instance.Parameters.MaxId.HasValue)
            {
                yield return string.Format("max_id={0}", instance.Parameters.MaxId.Value);
            }

            if (instance.Parameters.Count.HasValue)
            {
                yield return string.Format("count={0}", instance.Parameters.Count.Value);
            }

            if (instance.Parameters.Page.HasValue)
            {
                yield return string.Format("page={0}", instance.Parameters.Page.Value);
            }

            if (instance.Parameters.ReturnPerPage.HasValue)
            {
                yield return string.Format("rpp={0}", instance.Parameters.ReturnPerPage.Value);
            }

            if (!instance.Parameters.Text.IsNullOrBlank())
            {
                var format = instance.Parameters.Activity == "direct_messages"
                                 ? "text={0}"
                                 : "status={0}";

                var content = instance.Parameters.Text.UrlEncode();
                yield return string.Format(format, content);
            }

            if (instance.Parameters.InReplyToStatusId.HasValue)
            {
                yield return string.Format("in_reply_to_status_id={0}", instance.Parameters.InReplyToStatusId.Value);
            }

            if (instance.Parameters.UserId.HasValue)
            {
                string format;
                switch(instance.Parameters.Activity)
                {
                    case "friendships":
                        format = "user_a={0}";
                        break;
                    case "users":
                        format = "user_id={0}";
                        break;
                    default:
                        format = "user={0}";
                        break;
                }
                
                yield return string.Format(format, instance.Parameters.UserId.Value);
            }

            if (!instance.Parameters.UserScreenName.IsNullOrBlank())
            {
                string format;
                switch (instance.Parameters.Activity)
                {
                    case "friendships":
                        format = "user_a={0}";
                        break;
                    case "users":
                        format = "screen_name={0}";
                        break;
                    default:
                        format = "user={0}";
                        break;
                }
                
                yield return string.Format(format, instance.Parameters.UserScreenName.UrlEncode());
            }

            if (instance.Parameters.Follow.HasValue)
            {
                yield return string.Format("follow={0}", instance.Parameters.Follow.ToString().ToLower());
            }

            if (instance.Parameters.VerifyId.HasValue)
            {
                yield return string.Format("user_b={0}", instance.Parameters.VerifyId.ToString().ToLower());
            }

            if (!instance.Parameters.VerifyScreenName.IsNullOrBlank())
            {
                var format = instance.Parameters.Activity == "friendships"
                                 ? "user_b={0}"
                                 : "user={0}";

                yield return string.Format(format, instance.Parameters.VerifyScreenName.UrlEncode());
            }

            if (!instance.Profile.ProfileName.IsNullOrBlank())
            {
                yield return string.Format("name={0}", instance.Profile.ProfileName.UrlEncode());
            }

            if (!instance.Profile.ProfileLocation.IsNullOrBlank())
            {
                yield return string.Format("location={0}", instance.Profile.ProfileLocation.UrlEncode());
            }

            if (!instance.Profile.ProfileUrl.IsNullOrBlank())
            {
                yield return string.Format("url={0}", instance.Profile.ProfileUrl.UrlEncode());
            }

            if (!instance.Profile.ProfileDescription.IsNullOrBlank())
            {
                yield return string.Format("description={0}", instance.Profile.ProfileDescription.UrlEncode());
            }

            if (instance.Profile.ProfileDeliveryDevice.HasValue)
            {
                yield return string.Format("device={0}", instance.Profile.ProfileDeliveryDevice.ToString().ToLower());
            }

            if (!instance.Profile.ProfileBackgroundColor.IsNullOrBlank())
            {
                yield return string.Format("profile_background_color={0}", instance.Profile.ProfileBackgroundColor.UrlEncode());
            }

            if (!instance.Profile.ProfileTextColor.IsNullOrBlank())
            {
                yield return string.Format("profile_text_color={0}", instance.Profile.ProfileTextColor.UrlEncode());
            }

            if (!instance.Profile.ProfileLinkColor.IsNullOrBlank())
            {
                yield return string.Format("profile_link_color={0}", instance.Profile.ProfileLinkColor.UrlEncode());
            }

            if (!instance.Profile.ProfileSidebarFillColor.IsNullOrBlank())
            {
                yield return
                    string.Format("profile_sidebar_fill_color={0}", instance.Profile.ProfileSidebarFillColor.UrlEncode());
            }

            if (!instance.Profile.ProfileSidebarBorderColor.IsNullOrBlank())
            {
                yield return string.Format("profile_sidebar_border_color={0}", instance.Profile.ProfileLinkColor.UrlEncode());
            }

            if (instance.Parameters.GeoLocation != null)
            {
                yield return string.Format("lat={0}&long={1}", 
                    instance.Parameters.GeoLocation.Value.Latitude,
                    instance.Parameters.GeoLocation.Value.Longitude);
            }

            // Saved searches borrows the search builder
            if(instance.Parameters.Activity.Equals("saved_searches") &&
               instance.Parameters.Action.Equals("create"))
            {
                var format = "query={0}";
                var sb = new StringBuilder();
                
                var searchOperators = BuildSearchOperators(instance);
                var searchParameters = BuildSearchParameters(instance);

                var total = searchOperators.Count();
                var count = 0;
                foreach (var searchOperator in searchOperators)
                {
                    sb.Append(searchOperator);
                    count++;

                    if(total < count)
                    {
                        sb.Append(" ");
                    }
                }

                total = searchParameters.Count();
                count = 0;
                foreach(var searchParameter in searchParameters)
                {
                    sb.Append(searchParameter);
                    count++;

                    if (total < count)
                    {
                        sb.Append(" ");
                    }
                }

                yield return format.FormatWith(sb);
            }
        }

        public static IFluentTwitter AuthenticateAs(this IFluentTwitter instance, string username, string password)
        {
            instance.Authentication.Mode = AuthenticationMode.Basic;
            instance.Authentication.Authenticator = new FluentTwitterBasicAuth(username, password);

            return instance;
        }

        public static IFluentTwitter ExternallyAuthenticateAs( this IFluentTwitter instance, string username, string password )
        {
            instance.SecondaryAuthentication.Mode = AuthenticationMode.Basic;
            instance.SecondaryAuthentication.Authenticator = new FluentTwitterBasicAuth( username, password );

            return instance;
        }

        public static IFluentTwitter AuthenticateWith(this IFluentTwitter instance, string consumerKey, string consumerSecret, string token, string tokenSecret)
        {
            instance.Authentication.Mode = AuthenticationMode.OAuth;
            instance.Authentication.Authenticator = new FluentTwitterOAuth
                                                    {
                                                        Action="resource",
                                                        ConsumerKey = consumerKey,
                                                        ConsumerSecret = consumerSecret,
                                                        Token = token,
                                                        TokenSecret = tokenSecret
                                                    };
            return instance;
        }

        public static IFluentTwitter AuthenticateWith(this IFluentTwitter instance, string token, string tokenSecret)
        {
            ValidateConsumerCredentials(instance);

            var consumerKey = instance.ClientInfo.ConsumerKey;
            var consumerSecret = instance.ClientInfo.ConsumerSecret;

            instance.Authentication.Mode = AuthenticationMode.OAuth;
            instance.Authentication.Authenticator = new FluentTwitterOAuth
                                                    {
                                                        Action = "resource",
                                                        ConsumerKey = consumerKey,
                                                        ConsumerSecret = consumerSecret,
                                                        Token = token,
                                                        TokenSecret = tokenSecret
                                                    };
            return instance;
        }

        internal static void ValidateConsumerCredentials(this IFluentTwitter instance)
        {
            if (instance.ClientInfo == null ||
               instance.ClientInfo.ConsumerKey.IsNullOrBlank() ||
               instance.ClientInfo.ConsumerSecret.IsNullOrBlank())
            {
                throw new ValidationException("You need to provide a consumer key and secret, either in TwitterClientInfo, or to the overload of this method.");
            }
        }

        /// <summary>
        /// Calling this method will establish the asynchronous callback used when the request receives a response.
        /// </summary>
        /// <param name="instance">The current location in the fluent expression</param>
        /// <param name="callback">The callback executed when a request completes in the background</param>
        /// <returns>The current location in the fluent expression</returns>
        public static IFluentTwitter CallbackTo(this IFluentTwitter instance, WebCallback callback)
        {
            instance.Callback = callback;
            return instance;
        }

        public static IFluentTwitter RepeatEvery(this IFluentTwitter instance, TimeSpan timeSpan)
        {
            instance.RepeatInterval = timeSpan;
            instance.RepeatTimes = 0;
            return instance;
        }

        public static IFluentTwitter RepeatAfter(this IFluentTwitter instance, TimeSpan timeSpan, int times)
        {
            instance.RepeatInterval = timeSpan;
            instance.RepeatTimes = times;
            return instance;
        }

        /// <summary>
        /// Calling this method will enable mocking support, and return the Twitter object graph
        /// specified when the request is executed. 
        /// </summary>
        /// <param name="graph">A graph of objects expected in the response</param>
        /// <returns>The current location in the fluent expression</returns>
        /// <param name="instance">The current location in the fluent expression</param>
        public static IFluentTwitter Expect(this IFluentTwitter instance, IEnumerable<IModel> graph)
        {
            instance.Configuration.MockWebRequests = true;
            instance.Configuration.MockGraph = graph;
            return instance;
        }
    }
}